package com.uxsino.rule.nms.handler;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.uxsino.commons.enums.AlertOrigin;
import com.uxsino.commons.model.BaseNeClass;
// import com.uxsino.commons.model.RunStatus;
import com.uxsino.commons.utils.AlertCodeUtils;
import com.uxsino.commons.utils.CopyBeanUtils;
import com.uxsino.commons.utils.StringUtils;
import com.uxsino.rule.model.CompareType;
import com.uxsino.rule.model.CompsRange;
import com.uxsino.rule.model.DefaultTemple;
import com.uxsino.rule.model.ListCompareWay;
import com.uxsino.rule.nms.model.NmsCaller;
import com.uxsino.rule.nms.model.NmsReport;
import com.uxsino.rule.nms.model.SubRuleReport;
import com.uxsino.rule.nms.strategy.dto.IndicatorTableDto;
import com.uxsino.rule.nms.strategy.dto.RuleDto;
import com.uxsino.rule.nms.strategy.dto.ValidateAlertInfo;
import com.uxsino.rule.nms.strategy.dto.ValidateMsg;
import com.uxsino.rule.nms.strategy.model.CompositionSupport;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.mvel2.MVEL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

/**
 * 
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public abstract class BaseStrategy implements CompositionSupport {

    private static Logger LOG = LoggerFactory.getLogger(BaseStrategy.class);

    protected NmsCaller callData;

    protected DecimalFormat df = new DecimalFormat("0.##");

    protected static final int hit = 10;// 告警命中

    protected static final int not_hit = 1;// 告警恢复

    protected static final int exception_hit = -1;// 指标无数据

    // protected static final String DEF_ALERT_TPL = "【${资源名称}
    // ${IP地址}】的【${指标名称}】在${告警时间}产生【${告警级别}】级别的告警，内容为：【${部件名称}】的【${属性名称}】${比较方式}异常，当前值：${采集值}，阈值：${阈值}";

    // protected static final String DEF_RECOVER_TPL = "【${资源名称}
    // ${IP地址}】的【${指标名称}】在${告警时间}已恢复，内容为：【${部件名称}】的【${属性名称}】${比较方式}正常，当前值：${采集值}，阈值：${阈值}";

    // protected static final String DEF_UNUSABLE_TPL = "【${资源名称} ${IP地址}】不可用";

    // protected static final String DEF_USEABLE_TPL = "【${资源名称} ${IP地址}】恢复可用";

    // protected static final String DEF_COMP_CHANGE_TPL = "【${资源名称} ${IP地址}】的【${指标名称}】在${告警时间}部件变更";

    // protected static final String DEF_COMP_SAME_TPL = "【${资源名称} ${IP地址}】的【${指标名称}】在${告警时间}部件未变更";

    /**
     * 匹配校验逻辑并返回校验结果
     */
    abstract public boolean ruleMatch(JSONObject setting, String... value);

    /**
     * 根据设置生成阈值范围描述
     * 
     * @param setting
     * @return
     */
    abstract public String createAlertRange(JSONObject setting);

    /**
     * 按资源告警(整体的)校验流程
     */
    public NmsReport holisticVerifyProcess() {
        RuleDto rule = callData.getRule();
        JSONArray array = callData.getIndValue();
        IndicatorTableDto indicator = callData.getIndicator();
        boolean isComponent = "LIST".equals(indicator.getIndicatorType())
                && (indicator.getNoKey() == null || !indicator.getNoKey());
        JSONArray historyArray = callData.getHistoryValue();
        ValidateMsg msg = getMsg();
        boolean alertStatus = false;
        List<String> componentIds = getComponentIds(rule);
        List<ValidateAlertInfo> alertInfoList = new ArrayList<>();
        for (Object obj : JSON.parseArray(rule.getSettings())) {
            JSONObject setting = JSON.parseObject(obj.toString());
            if (isComponent && CompareType.change.equals(rule.getCompareType())) {// 部件变更
                // 部件范围为“不限”时，没有指定的部件，是动态匹配部件表所有数据。此处不设置告警部件，否则可能会使告警无法恢复
                // msg.setComponentId(getComponentIds(callData.getComps()).stream().distinct().collect(Collectors.joining(",")));
                ListCompareWay compareWay = ListCompareWay.valueOf(setting.getString("compareWay"));
                JSONObject tmpSetting = new JSONObject();
                tmpSetting.putAll(setting);
                tmpSetting.put("currentList", org.apache.commons.lang3.StringUtils.join(getComponentIds(array), ","));
                tmpSetting.put("baseList",
                    org.apache.commons.lang3.StringUtils.join(getComponentIds(callData.getComps()), ","));
                boolean warning = ruleMatch(tmpSetting, null, null);
                if (warning) {
                    alertStatus = true;
                    msg.setCurrentValue(compareWay.getText());
                } else {
                    msg.setCurrentValue(ListCompareWay.same.equals(compareWay) ? ListCompareWay.change.getText()
                            : ListCompareWay.same.getText());
                }
                ValidateAlertInfo alert = dealWithValidateResult(setting, warning, msg);
                alertInfoList.add(alert);
            } else {
                List<ValidateAlertInfo> tmpAlertList = new ArrayList<>();
                for (Object dataObj : array) {
                    JSONObject result = null;
                    try {
                        result = JSONObject.parseObject(dataObj.toString());// 当前检测数据
                    } catch (Exception e) {
                        LOG.error("", e);
                        continue;
                    }
                    String currentComponentId = "";
                    String currentComponentName = "";
                    if (isComponent) {
                        try {
                            currentComponentId = result.containsKey("identifier") ? result.getString("identifier")
                                    : currentComponentId;
                            currentComponentName = MVEL.evalToString(indicator.getComponentNameFormula(), result);
                        } catch (Exception e) {
                            LOG.error("MVEL.evalToString error.{} -> {}", indicator,
                                indicator.getComponentNameFormula() + ", " + dataObj, e);
                        }
                        if (!validateEnable(currentComponentId, componentIds)) {// 校验使能
                            continue;
                        }
                    }
                    boolean warning = verifierUnit(result, currentComponentId, rule, setting, historyArray, msg, false);
                    if (warning) {
                        alertStatus = true;
                    }
                    // 校验过程中的数据记录--用于本次校验完成之后的告警...
                    msg.setComponentId(currentComponentId);
                    msg.setComponentName(currentComponentName);
                    ValidateAlertInfo alert = dealWithValidateResult(setting, warning, msg);
                    tmpAlertList.add(alert);
                    if ("LIST".equals(indicator.getIndicatorType())
                            && (indicator.getNoKey() != null && indicator.getNoKey()) && warning) {// 普通列表，若其中一个指标值告警即表示整个列表告警
                        tmpAlertList = Lists.newArrayList(tmpAlertList.get(tmpAlertList.size() - 1));// 只取发生告警的那一条
                        break;
                    }
                }
                alertInfoList.addAll(tmpAlertList);
            }
        }
        NmsReport report = new NmsReport();
        report.setIndStatus(alertStatus ? hit : not_hit);
        report.setAlertStatus(alertStatus);
        report.setAlertList(alertInfoList);
        return report;
    }

    protected List<String> getComponentIds(JSONArray arr) {// 指标值或部件表中的部件ID
        List<String> componentIds = null;
        if (arr != null && !arr.isEmpty()) {
            componentIds = arr.stream().map(obj -> ((JSONObject) obj).getString("identifier"))
                .collect(Collectors.toList());
        }
        return componentIds == null ? Lists.newArrayList() : componentIds;
    }

    protected Map<String, String> getComponents(JSONArray arr, String componentNameFormula) {// 指标值或部件表中的部件
        Map<String, String> componentMap = new HashMap<>();
        if (arr != null && !arr.isEmpty()) {
            for (int i = 0; i < arr.size(); i++) {
                JSONObject obj = arr.getJSONObject(i);
                if (StringUtils.isBlank(obj.getString("identifier"))) {
                    continue;
                }
                String componentName = null;
                try {
                    componentName = obj.containsKey("componentName") ? obj.getString("componentName")
                            : MVEL.evalToString(componentNameFormula, obj);
                } catch (Exception e) {
                    LOG.error("MVEL.evalToString error. -> " + componentNameFormula + ", " + obj, e);
                }
                componentMap.put(obj.getString("identifier"),
                    StringUtils.isBlank(componentName) ? obj.getString("identifier") : componentName);
            }
        }
        return componentMap;
    }

    protected List<String> getComponentIds(RuleDto rule) {// 策略中配置的部件ID
        List<String> componentIds = null;
        String neId = callData.getNe().getId();
        if (rule != null && CompsRange.selected.equals(rule.getCompsRange())
                && StringUtils.isNotEmpty(rule.getNeComps())) {
            JSONArray neComps = JSONArray.parseArray(rule.getNeComps());
            componentIds = neComps.stream().filter(neComp -> neId.equals(((JSONObject) neComp).getString("neId")))
                .map(neComp -> ((JSONObject) neComp).getString("identifier")).collect(Collectors.toList());
        }
        return componentIds == null ? Lists.newArrayList() : componentIds;
    }

    protected Map<String, String> getComponents(RuleDto rule) {// 策略中配置的部件
        Map<String, String> componentMap = null;
        String neId = callData.getNe().getId();
        if (rule != null && CompsRange.selected.equals(rule.getCompsRange())
                && StringUtils.isNotEmpty(rule.getNeComps())) {
            JSONArray neComps = JSONArray.parseArray(rule.getNeComps());
            componentMap = neComps.stream().filter(neComp -> neId.equals(((JSONObject) neComp).getString("neId")))
                .collect(Collectors.toMap(neComp -> ((JSONObject) neComp).getString("identifier"),
                    neComp -> ((JSONObject) neComp).getString("componentName")));
        }
        return componentMap == null ? new HashMap<>() : componentMap;
    }

    public boolean verifierUnit(JSONObject result, String currentComponentId, RuleDto rule, JSONObject setting,
        JSONArray historyArray, ValidateMsg msg, boolean isComposition) {
        String hisvalue = null;
        if (historyArray != null && historyArray.size() > 0) {
            for (Object hisObj : historyArray) {
                JSONObject hisJson = JSONObject.parseObject(hisObj.toString());
                if (currentComponentId.equals(hisJson.getString("identifier"))) {
                    hisvalue = hisJson.getString(rule.getFieldName());
                    break;
                }
            }
        }
        boolean warning = false;
        if (!"poweredOff".equals(result.getString("powerState"))) {
            String value = getCurrentValueFromCollectData(result, rule.getFieldName());
            msg.setCurrentValue(isComposition ? rule.getFieldText() + "=" + value + "," : value);
            if (StringUtils.isNotEmpty(value)) {
                // 是否匹配规则
                warning = ruleMatch(setting, value, hisvalue);
            }
        }
        return warning;
    }

    public ValidateMsg getMsg() {
        RuleDto rule = callData.getRule();
        IndicatorTableDto indicator = callData.getIndicator();
        ValidateMsg msg = new ValidateMsg();
        msg.setIndicator(indicator.getLabel());
        msg.setIndicatorId(indicator.getName());
        // 记录指标的资源类型，可能用于vmware资源规则校验的判断上
        // 设置indicator的component type name
        // msg.setComponentType(indicator.getComponentType());
        // msg.setComponentKey(indicator.getComponentKey());
        // 设置网元的信息
        msg.setNe(callData.getNe());
        msg.setIndNeclass(callData.getNe().getNeClass().name());
        // 设置检测时间
        msg.setTime(callData.getTime());
        msg.setRuleId(rule.getId());
        msg.setFieldConfId(rule.getFieldConfId());

        msg.setField(rule.getFieldName());
        msg.setFieldText(rule.getFieldText());
        return msg;
    }

    /**
     * 生成校验策略的告警内容
     * 
     * @param msg
     * @param alertTemplate
     * @param valiStatus
     * @return
     */
    public String createAlertBrief(ValidateMsg msg, String alertTemplate, boolean valiStatus) {
        Map<String, String> varValMap = new HashMap<>();
        varValMap.put("${资源名称}", msg.getNe().getName());
        varValMap.put("${IP地址}", msg.getNe().getIp());
        varValMap.put("${告警时间}", msg.getTime());
        varValMap.put("${指标名称}", msg.getIndicator());
        varValMap.put("${属性名称}", StringUtils.isNotBlank(msg.getFieldText()) ? msg.getFieldText() : msg.getIndicator());
        varValMap.put("${比较方式}", callData.getRule().getCompareType().getText());
        varValMap.put("${采集值}", msg.getCurrentValue());
        varValMap.put("${阈值}", msg.getRange());
        String tpl = "";
        if ("useable_state".equals(msg.getIndicatorId())) {// 可用性告警
            tpl = valiStatus
                    ? (StringUtils.isNotBlank(alertTemplate) ? alertTemplate : DefaultTemple.DEF_UNUSABLE_TPL.getText())
                    : DefaultTemple.DEF_USEABLE_TPL.getText();
            return replaceVar(tpl, varValMap);
        } else if (CompareType.change.equals(callData.getRule().getCompareType())) {
            if (valiStatus) {
                if (StringUtils.isNotBlank(alertTemplate)) {
                    tpl = alertTemplate;
                } else {
                    tpl = DefaultTemple.DEF_COMP_CHANGE_TPL.getText();
                    Map<String, String> indComp = getComponents(callData.getIndValue(),
                        callData.getIndicator().getComponentNameFormula());
                    List<String> indCompIds1 = Lists.newArrayList(indComp.keySet());
                    List<String> indCompIds2 = Lists.newArrayList(indComp.keySet());
                    Map<String, String> comp = getComponents(callData.getComps(), null);
                    List<String> compIds = Lists.newArrayList(comp.keySet());
                    indCompIds1.removeAll(compIds);
                    if (!indCompIds1.isEmpty()) {
                        List<String> extraCompIds = indCompIds1.size() > 3 ? indCompIds1.subList(0, 3) : indCompIds1;
                        List<String> extraCompNames = extraCompIds.stream()
                            .map(compId -> indComp.getOrDefault(compId, compId)).collect(Collectors.toList());
                        tpl += "，多出部件：" + org.apache.commons.lang3.StringUtils.join(extraCompNames, "、")
                                + (indCompIds1.size() > 3 ? "等" : "");
                    }
                    compIds.removeAll(indCompIds2);
                    if (!compIds.isEmpty()) {
                        List<String> lostCompIds = compIds.size() > 3 ? compIds.subList(0, 3) : compIds;
                        List<String> lostCompNames = lostCompIds.stream()
                            .map(compId -> comp.getOrDefault(compId, compId)).collect(Collectors.toList());
                        tpl += "，缺失部件：" + org.apache.commons.lang3.StringUtils.join(lostCompNames, "、")
                                + (compIds.size() > 3 ? "等" : "");
                    }
                }
            } else {
                tpl = DefaultTemple.DEF_COMP_SAME_TPL.getText();
            }
        } else {
            tpl = valiStatus
                    ? (StringUtils.isNotBlank(alertTemplate) ? alertTemplate : DefaultTemple.DEF_ALERT_TPL.getText())
                    : DefaultTemple.DEF_RECOVER_TPL.getText();
        }
        if (StringUtils.isNotBlank(msg.getComponentName()) && !"null".equals(msg.getComponentName())) {
            varValMap.put("${部件名称}", msg.getComponentName());
        } else if (!valiStatus || StringUtils.isBlank(alertTemplate)) {
            varValMap.put("【${部件名称}】的", "");
        }
        return replaceVar(tpl, varValMap);
    }

    /**
     * 替换字符串中的变量
     * 
     * @param str       字符串
     * @param varValMap 变量和值
     * @return
     */
    private String replaceVar(String str, Map<String, String> varValMap) {
        if (StringUtils.isBlank(str) || varValMap == null || varValMap.isEmpty()) {
            return str;
        }
        for (Entry<String, String> entry : varValMap.entrySet()) {
            String variable = entry.getKey();
            String value = entry.getValue();
            if (!str.contains(variable) || value == null) {
                continue;
            }
            str = str.replace(variable, value);
        }
        return str;
    }

    /**
     * 生成alertCode
     * 
     * @param setting
     * @param msg
     * @return
     */
    public String createAlertCode(JSONObject setting, ValidateMsg msg) {
        return AlertCodeUtils.getNeIndAlertCode(msg.getNe().getId(),
            StringUtils.isBlank(msg.getComponentId()) ? null : msg.getComponentId(), msg.getIndicatorId(),
            msg.getField(), msg.getRuleId(), setting);
    }

    /**
     * 判断当前部件部件是否需要校验
     * 
     * @param currentComponent 当前部件标识
     * @param componentIds     用户设置的部件
     * @return
     */
    public boolean validateEnable(String currentComponentId, List<String> componentIds) {
        if (callData.getVmInfos() != null && callData.getVmInfos().size() > 0) {
            return callData.getVmInfos().get(currentComponentId) == null
                    || callData.getVmInfos().get(currentComponentId);
        }
        return CompsRange.all.equals(callData.getRule().getCompsRange()) || StringUtils.isBlank(currentComponentId)
                || componentIds.contains(currentComponentId);
    }

    /**
     * 从指标数据中获取当前匹配的规则对应的属性值
     * 
     * @param result
     * @param fieldName
     * @return
     */
    public String getCurrentValueFromCollectData(JSONObject result, String fieldName) {
        if (StringUtils.isEmpty(fieldName)) {
            return result.getString("result");
        } else {
            String cv = result.getString(fieldName);
            if (cv != null && (cv.contains("E") || cv.contains("e"))
                    && "NUMBER".equals(getFieldType(callData.getIndicator(), fieldName))) {// 转换科学记数法
                try {
                    return new BigDecimal(cv).toPlainString();
                } catch (Exception e) {
                    LOG.error("E-notation error! {}", cv);
                }
            }
            return cv;
        }
    }

    /**
     * 获取属性类型
     * 
     * @param indicator
     * @param fieldName
     * @return
     */
    protected String getFieldType(IndicatorTableDto indicator, String fieldName) {
        if (indicator == null || fieldName == null || indicator.getFields() == null
                || indicator.getFields().isEmpty()) {
            return null;
        }
        String fieldType = null;
        JSONArray fields = indicator.getFields();
        for (int i = 0; i < fields.size(); i++) {
            JSONObject fldObj = fields.getJSONObject(i);
            if (fldObj != null && fieldName.equals(fldObj.getString("name"))) {
                fieldType = fldObj.getString("fieldType");
                break;
            }
        }
        return fieldType;
    }

    /**
     * 处理校验结果，判断是否触发告警
     * 
     * @param setting
     * @param valiStatus
     * @param msg
     */
    public ValidateAlertInfo dealWithValidateResult(JSONObject setting, boolean valiStatus, ValidateMsg msg) {
        Long eventId = setting.getLong("handler");
        int level = setting.getIntValue("level");
        long flap = setting.getLongValue("flap");

        String alertCode = "";
        if (BaseNeClass.virtualization.equals(msg.getNe().getNeClass().getBaseNeClass())) {// 按部件告警
            alertCode = createAlertCode(setting, msg);
        } else {
            alertCode = createAlertCode(setting, msg);
        }
        msg.setRange(createAlertRange(setting));
        ValidateAlertInfo alert = new ValidateAlertInfo();
        alert.setEventId(eventId);
        alert.setLevel(level);
        alert.setAlertCode(alertCode);
        alert.setAlertBrief(createAlertBrief(msg, setting.getString("template"), valiStatus));
        alert.setObjectId(msg.getNe().getId());
        alert.setSource(AlertOrigin.POLLING.toString());
        msg.setComponentName("");
        ValidateMsg sendMsg = new ValidateMsg();
        CopyBeanUtils.copyProperties(msg, sendMsg);
        alert.setMsg(sendMsg);
        alert.setFlap(flap);
        alert.setType(valiStatus ? "alert" : "recover");
        // doEvent(alert, hitValue, flap, msg);
        return alert;
    }

    /**
     * 应用告警模板
     */
    public String createTemplateContent(JSONArray indicatorFields, String replaceTag, String templateCopy,
        JSONObject result) {
        try {
            boolean notFound = true;
            String fileLable = replaceTag.substring(2, replaceTag.length() - 1);
            for (Object obj : indicatorFields) {
                JSONObject field = JSONObject.parseObject(obj.toString());
                if (field.getString("label").equals(fileLable)) {
                    notFound = false;
                    String targetStr = result.getString(field.getString("name"));
                    templateCopy = templateCopy.replace(replaceTag,
                        (targetStr == null ? replaceTag : formatCurrentValue(targetStr)));
                    break;
                }
            }
            if (notFound) {
                templateCopy = templateCopy.replace(replaceTag, replaceTag);
                LOG.info("没有找到占位符所指属性！！");
            }
        } catch (Exception e) {
            // TODO: handle exception
            LOG.error("匹配占位符错误！！{}", e);
        }
        return templateCopy;
    }

    public String formatCurrentValue(String value) {
        if ("NaN".equals(value)) {
            return "无";
        }
        try {
            Double number = Double.valueOf(value);
            return df.format(number);
        } catch (Exception e) {
            return value;
        }
    }

    @Override
    public SubRuleReport support4Holistic() {
        NmsReport report = holisticVerifyProcess();
        SubRuleReport srr = new SubRuleReport();
        srr.setAlertStatus(report.getAlertStatus());
        List<ValidateAlertInfo> alertList = report.getAlertList();
        String subAlertContent = "";
        if (alertList != null && !alertList.isEmpty()) {
            for (ValidateAlertInfo alert : alertList) {
                ValidateMsg msg = alert.getMsg();
                if (msg != null) {
                    subAlertContent += msg.getFieldText() + "=" + msg.getCurrentValue() + ",";
                }
            }
            if (subAlertContent.endsWith(",")) {
                subAlertContent = subAlertContent.substring(0, subAlertContent.lastIndexOf(","));
            }
        }
        srr.setSubAlertContent(subAlertContent);
        return srr;
    }

    @Override
    public SubRuleReport support4Modular(JSONObject compData) {
        RuleDto rule = callData.getRule();
        JSONArray historyArray = callData.getHistoryValue();
        String currentComponentId = compData.containsKey("identifier") ? compData.getString("identifier") : "";
        ValidateMsg msg = new ValidateMsg();
        boolean valiStatus = false;
        for (Object obj : JSON.parseArray(rule.getSettings())) {
            JSONObject setting = JSON.parseObject(obj.toString());
            boolean warning = verifierUnit(compData, currentComponentId, rule, setting, historyArray, msg, true);
            valiStatus = valiStatus || warning;
        }
        SubRuleReport srr = new SubRuleReport();
        srr.setAlertStatus(valiStatus);
        srr.setSubAlertContent(msg.getCurrentValue());
        return srr;
    }
}